﻿// Copyright 2012 Jack Bulan (agentgeo)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace GemWorkshop.Formats.Nitro
{
    public class ROMRepacker
    {
        /// <summary>
        /// Repacks the ROM based upon the data loaded, and extracted by the unpacker.
        /// </summary>
        /// <param name="unpacker"></param>
        public TimeSpan DoRom(ROMUnpacker unpacker)
        {
            Stopwatch timer = Stopwatch.StartNew();

            //Console.WriteLine("ROM Repacker -- v1.1\nCopyright (c) 2012 Agentgeo");
            BinaryWriter output = new BinaryWriter(File.OpenWrite(unpacker.RomFileName));
            // Repack file data.
            //Console.WriteLine("Repacking files...");
            for (ushort iFile = 0; iFile < unpacker.FileCount; iFile++)
            {
                string FilePath = unpacker.FilePaths[iFile];
                //Console.WriteLine("> Repacking file {0} - {1}...", iFile, FilePath);
                byte[] buffer = System.IO.File.ReadAllBytes(FilePath);

                output.BaseStream.Position = unpacker.AllocationTable.Offsets[unpacker.ReverseFileNumbers[iFile]];
                output.Write(buffer);
            }

            // Repack Arm7, Arm9, overlay
            //Console.WriteLine("> Repacking ARM7...");
            output.BaseStream.Position = unpacker.Header.Arm7Offset;
            byte[] armBuffer = File.ReadAllBytes(unpacker.DirectoryName + @"\arm7.bin");
            output.Write(armBuffer);

            //Console.WriteLine("> Repacking ARM9...");
            output.BaseStream.Position = unpacker.Header.Arm9Offset;
            armBuffer = File.ReadAllBytes(unpacker.DirectoryName + @"\arm9.bin");
            output.Write(armBuffer);

            //Console.WriteLine("> Repacking ARM7 Overlay...");
            output.BaseStream.Position = unpacker.Header.Arm7OverlayOffset;
            armBuffer = File.ReadAllBytes(unpacker.DirectoryName + @"\arm7overlay.bin");
            output.Write(armBuffer);

            //Console.WriteLine("> Repacking ARM9 Overlay...");
            output.BaseStream.Position = unpacker.Header.Arm9OverlayOffset;
            armBuffer = File.ReadAllBytes(unpacker.DirectoryName + @"\arm9overlay.bin");
            output.Write(armBuffer);

            output.Close();
            output.Dispose();

            timer.Stop();
            return timer.Elapsed;
        }

        public TimeSpan CreateRom(ROMUnpacker u, string o)
        {
            Stopwatch timer = Stopwatch.StartNew();

            BinaryWriter bw = new BinaryWriter(File.Create(o));

            // Write the header
            #region Header
            ROMHeader header = u.Header;
            bw.Write(Encoding.UTF8.GetBytes(header.Title));
            bw.Write(Encoding.UTF8.GetBytes(header.Code));
            bw.Write(Encoding.UTF8.GetBytes(header.Maker));

            bw.Write(header.DeviceCode);
            bw.Write(header.EncryptionSeed);
            bw.Write(header.FileLength);
            for (int i = 0; i < 9; i++)
                bw.Write((byte)0); // Reserved

            bw.Write(header.Version);
            bw.Write(header.InternalFlags);

            bw.Write(header.Arm9Offset); // We'll have to change this!
            bw.Write(header.Arm9Entry);
            bw.Write(header.Arm9Load);
            bw.Write(header.Arm9Length);

            bw.Write(header.Arm7Offset); // We'll have to change this!
            bw.Write(header.Arm7Entry);
            bw.Write(header.Arm7Load);
            bw.Write(header.Arm7Length);

            bw.Write(header.FNTOffset); // Also change these
            bw.Write(header.FNTSize);
            bw.Write(header.FATOffset);
            bw.Write(header.FATSize);

            bw.Write(header.Arm9OverlayOffset); // AND these
            bw.Write(header.Arm9OverlaySize);
            bw.Write(header.Arm7OverlayOffset);
            bw.Write(header.Arm7OverlaySize);

            bw.Write(header.NormalRegisterSettings);
            bw.Write(header.SecureRegisterSettings);

            bw.Write(header.BannerOffset); // AND THIS

            bw.Write(header.SecureAreaCRC);
            bw.Write(header.SecureTransferTimeout);

            bw.Write(header.Arm9AutoLoad);
            bw.Write(header.Arm7AutoLoad);

            bw.Write(header.SecureDisable);

            bw.Write(header.ROMLength);
            bw.Write(header.HeaderLength);
            for (int i = 0; i < 56; i++)
                bw.Write((byte)0);

            bw.Write(header.NintendoLogo);
            bw.Write(header.NintendoLogoCRC);
            bw.Write(header.HeaderCRC);
            
            // Fill remaining header space (debug stuff)
            int rem = (int)(header.HeaderLength - bw.BaseStream.Position);
            for(int i = 0; i < rem; i++)
                bw.Write((byte)0);

            #endregion

            #region Arm 9

            uint start = (uint)bw.BaseStream.Position;
            bw.Write(File.ReadAllBytes(u.DirectoryName + "\\arm9.bin"));

            // Fix the header
            long iPos = bw.BaseStream.Position;
            bw.Seek(0x20, SeekOrigin.Begin);
            bw.Write(start);
            bw.BaseStream.Position = iPos;

            // Write empty space (we like even numbers)
            WriteAlignment(bw, 0x1000);

            #endregion

            #region Arm 9 Overlay

            start = (uint)bw.BaseStream.Position;
            bw.Write(File.ReadAllBytes(u.DirectoryName + "\\arm9overlay.bin"));

            // Fix the header
            iPos = bw.BaseStream.Position;
            bw.Seek(0x50, SeekOrigin.Begin);
            bw.Write(start);
            bw.BaseStream.Position = iPos;

            // Write empty space (we like even numbers)
            rem = (int)bw.BaseStream.Position % 512;
            for (int i = rem; i < 512; i++)
                bw.Write((byte)0xFF);

            #endregion

            bw.Close();
            bw.Dispose();

            timer.Stop();
            return timer.Elapsed;
        }

        private void WriteAlignment(BinaryWriter bw, int alignment)
        {
            int rem = (int)bw.BaseStream.Position % alignment;
            if (rem > 0)
            {
                for (int i = rem; i < alignment; i++)
                {
                    bw.Write((byte)0xFF);
                }
            }
        }
    }
}
